package com.wg.db.ds;

import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

/**
 * 数据源配置类
 * 支持灵活的连接池参数配置、参数校验及时区验证
 *
 * @author 少爷123
 */
public class DataSourceConfig {
    private static final Log log = LogFactory.get();

    /**
     * 默认连接池参数（常量定义提高可读性）
     */
    private static final int DEFAULT_INIT_SIZE = 2;
    /**
     * 默认最大连接数
     */
    private static final int DEFAULT_MAX_SIZE = 2;
    /**
     * 默认最大等待时间（毫秒）
     */
    private static final long DEFAULT_MAX_WAIT_MILLIS = 180000;
    /**
     * 默认校验查询语句
     */
    private static final String DEFAULT_VALIDATION_QUERY = "SELECT 1";
    /**
     * 默认连接校验时间间隔（毫秒）
     */
    private static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60000;

    /**
     * 数据库地址（不含数据库名）
     */
    private String url;
    /**
     * 用户名
     */
    private String user;
    /**
     * 密码
     */
    private String password;
    /**
     * 数据库名
     */
    private String db;
    /**
     * 时区（如Asia/Shanghai）
     */
    private String timezone;
    /**
     * 字符编码
     */
    private String characterEncoding;
    /**
     * 是否启用SSL
     */
    private boolean useSSL;
    /**
     * 自动重连
     */
    private boolean autoReconnect;
    /**
     * 故障转移时只读
     */
    private boolean failOverReadOnly;
    /**
     * 初始连接数
     */
    private int initSize = DEFAULT_INIT_SIZE;
    /**
     * 最大连接数
     */
    private int maxSize = DEFAULT_MAX_SIZE;
    /**
     * 最大等待时间（毫秒）
     */
    private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
    /**
     * 是否缓存PreparedStatement
     */
    private boolean poolPreparedStatements = false;
    /**
     * 最大缓存的PreparedStatement数量
     */
    private int maxOpenPreparedStatements = -1;
    /**
     * 是否使用全局统计
     */
    private boolean useGlobalDataSourceStat = false;
    /**
     * 动态附加参数（如连接属性）
     */
    private final Map<String, Object> arguments = new HashMap<>(8);
    /**
     * 数据库驱动类（可配置，默认MySQL）
     */
    private String driverClassName = "com.mysql.cj.jdbc.Driver";

    /**
     * 构建Druid数据源实例
     */
    public DataSource buildDataSource() throws Exception {
        validateConfig();
        DruidDataSource dataSource = new DruidDataSource();
        configureDataSource(dataSource);
        dataSource.init();
        return dataSource;
    }

    /**
     * 校验配置有效性
     */
    private void validateConfig() {
        if (StrUtil.isBlank(url)) {
            throw new IllegalArgumentException("数据库地址(url)不能为空");
        }
        if (StrUtil.isBlank(user)) {
            throw new IllegalArgumentException("用户名(user)不能为空");
        }
        if (StrUtil.isBlank(password)) {
            throw new IllegalArgumentException("密码(password)不能为空");
        }
        if (StrUtil.isBlank(db)) {
            throw new IllegalArgumentException("数据库名(db)不能为空");
        }
        if (StrUtil.isBlank(timezone)) {
            TimeZone defaultTz = TimeZone.getDefault();
            this.timezone = defaultTz.getID();
            log.warn("时区(timezone)未配置，使用系统默认时区: {}", this.timezone);
        } else {
            if (TimeZone.getTimeZone(timezone) == TimeZone.getTimeZone("GMT+0:00")) {
                throw new IllegalArgumentException(StrUtil.format("无效的时区配置(timezone): {}", timezone));
            }
        }
        if (StrUtil.isBlank(characterEncoding)) {
            this.characterEncoding = "UTF-8";
        }
    }

    /**
     * 动态配置Druid数据源参数
     */
    private void configureDataSource(DruidDataSource dataSource) {
        // 基础连接参数
        dataSource.setUrl(buildJdbcUrl());
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(user);
        dataSource.setPassword(password);

        // 连接池大小配置
        dataSource.setInitialSize(initSize);
        dataSource.setMinIdle(initSize);
        dataSource.setMaxActive(maxSize);
        dataSource.setMaxWait(maxWaitMillis);

        // 连接有效性检查
        dataSource.setValidationQuery(DEFAULT_VALIDATION_QUERY);
        dataSource.setTimeBetweenEvictionRunsMillis(DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
        dataSource.setTestWhileIdle(true); // 空闲时检查连接有效性

        // 高级配置
        dataSource.setPoolPreparedStatements(poolPreparedStatements);
        dataSource.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
        dataSource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
    }

    /**
     * 构建完整的JDBC URL（包含动态参数）
     */
    private String buildJdbcUrl() {
        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append("jdbc:mysql://")
                .append(url)
                .append("/")
                .append(db)
                .append("?");

        // 固定参数
        appendParam(urlBuilder, "serverTimezone", timezone);
        appendParam(urlBuilder, "characterEncoding", characterEncoding);
        appendParam(urlBuilder, "useSSL", useSSL);
        appendParam(urlBuilder, "autoReconnect", autoReconnect);
        appendParam(urlBuilder, "failOverReadOnly", failOverReadOnly);

        // 动态附加参数
        arguments.forEach((key, value) ->
                appendParam(urlBuilder, key, value)
        );

        return urlBuilder.toString();
    }

    /**
     * 辅助方法：向URL追加参数（避免空值）
     */
    private void appendParam(StringBuilder builder, String key, Object value) {
        if (value != null) {
            builder.append("&").append(key).append("=").append(value);
        }
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDb() {
        return db;
    }

    public void setDb(String db) {
        this.db = db;
    }

    public String getTimezone() {
        return timezone;
    }

    public void setTimezone(String timezone) {
        this.timezone = timezone;
    }

    public String getCharacterEncoding() {
        return characterEncoding;
    }

    public void setCharacterEncoding(String characterEncoding) {
        this.characterEncoding = characterEncoding;
    }

    public boolean isUseSSL() {
        return useSSL;
    }

    public void setUseSSL(boolean useSSL) {
        this.useSSL = useSSL;
    }

    public boolean isAutoReconnect() {
        return autoReconnect;
    }

    public void setAutoReconnect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
    }

    public boolean isFailOverReadOnly() {
        return failOverReadOnly;
    }

    public void setFailOverReadOnly(boolean failOverReadOnly) {
        this.failOverReadOnly = failOverReadOnly;
    }

    public int getInitSize() {
        return initSize;
    }

    public void setInitSize(int initSize) {
        this.initSize = initSize;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public long getMaxWaitMillis() {
        return maxWaitMillis;
    }

    public void setMaxWaitMillis(long maxWaitMillis) {
        this.maxWaitMillis = maxWaitMillis;
    }

    public boolean isPoolPreparedStatements() {
        return poolPreparedStatements;
    }

    public void setPoolPreparedStatements(boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public int getMaxOpenPreparedStatements() {
        return maxOpenPreparedStatements;
    }

    public void setMaxOpenPreparedStatements(int maxOpenPreparedStatements) {
        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    }

    public boolean isUseGlobalDataSourceStat() {
        return useGlobalDataSourceStat;
    }

    public void setUseGlobalDataSourceStat(boolean useGlobalDataSourceStat) {
        this.useGlobalDataSourceStat = useGlobalDataSourceStat;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Map<String, Object> getArguments() {
        return new HashMap<>(arguments);
    }

    public void addArgument(String key, Object value) {
        arguments.put(key, value);
    }
}